/*
 *  wslua_util.c
 *
 * (c) 2006, Luis E. Garcia Ontanon <luis@ontanon.org>
 *
 * Wireshark - Network traffic analyzer
 * By Gerald Combs <gerald@wireshark.org>
 * Copyright 1998 Gerald Combs
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "config.h"

/* WSLUA_MODULE Utility Utility Functions */

#include "wslua.h"
#include <math.h>
#include <epan/stat_tap_ui.h>


WSLUA_FUNCTION wslua_get_version(lua_State* L) { /* Gets the Wireshark version as a string. */
    const gchar* str = VERSION;
    lua_pushstring(L,str);
    WSLUA_RETURN(1); /* The version string, e.g. "3.2.5". */
}


static gchar* current_plugin_version = NULL;

const gchar* get_current_plugin_version(void) {
    return current_plugin_version ? current_plugin_version : "";
}

void clear_current_plugin_version(void) {
    if (current_plugin_version != NULL) {
        g_free(current_plugin_version);
        current_plugin_version = NULL;
    }
}

WSLUA_FUNCTION wslua_set_plugin_info(lua_State* L) {
    /*
    Set a Lua table with meta-data about the plugin, such as version.

    The passed-in Lua table entries need to be keyed/indexed by the following:

     * "version" with a string value identifying the plugin version (required)
     * "description" with a string value describing the plugin (optional)
     * "author" with a string value of the author's name(s) (optional)
     * "repository" with a string value of a URL to a repository (optional)

    Not all of the above key entries need to be in the table. The 'version'
    entry is required, however. The others are not currently used for anything, but
    might be in the future and thus using them might be useful. Table entries keyed
    by other strings are ignored, and do not cause an error.

    ===== Example

    [source,lua]
    ----
    local my_info = {
        version = "1.0.1",
        author = "Jane Doe",
        repository = "https://github.com/octocat/Spoon-Knife"
    }

    set_plugin_info(my_info)
    ----

    @since 1.99.8
    */
#define WSLUA_ARG_set_plugin_info_TABLE 1 /* The Lua table of information. */

    if ( lua_istable(L,WSLUA_ARG_set_plugin_info_TABLE) ) {
        int top;
        lua_getfield(L, WSLUA_ARG_set_plugin_info_TABLE, "version");
        top = lua_gettop(L);
        if (lua_isstring(L, top)) {
            clear_current_plugin_version();
            current_plugin_version = g_strdup( luaL_checkstring(L, top) );
            /* pop the string */
            lua_pop(L, 1);
        }
        else {
            return luaL_error(L,"the Lua table must have a 'version' key entry with a string value");
        }
    } else {
        return luaL_error(L,"a Lua table with at least a 'version' string entry");
    }

    return 0;
}


WSLUA_FUNCTION wslua_format_date(lua_State* LS) { /* Formats an absolute timestamp into a human readable date. */
#define WSLUA_ARG_format_date_TIMESTAMP 1 /* A timestamp value to convert. */
    lua_Number timestamp = luaL_checknumber(LS,WSLUA_ARG_format_date_TIMESTAMP);
    nstime_t then;
    gchar* str;

    then.secs = (guint32)(floor(timestamp));
    then.nsecs = (guint32) ( (timestamp-(double)(then.secs))*1000000000);
    str = abs_time_to_str(NULL, &then, ABSOLUTE_TIME_LOCAL, TRUE);
    lua_pushstring(LS,str);
    wmem_free(NULL, str);

    WSLUA_RETURN(1); /* A string with the formated date */
}

WSLUA_FUNCTION wslua_format_time(lua_State* LS) { /* Formats a relative timestamp in a human readable time. */
#define WSLUA_ARG_format_time_TIMESTAMP 1 /* A timestamp value to convert. */
    lua_Number timestamp = luaL_checknumber(LS,WSLUA_ARG_format_time_TIMESTAMP);
    nstime_t then;
    gchar* str;

    then.secs = (guint32)(floor(timestamp));
    then.nsecs = (guint32) ( (timestamp-(double)(then.secs))*1000000000);
    str = rel_time_to_str(NULL, &then);
    lua_pushstring(LS,str);
    wmem_free(NULL, str);

    WSLUA_RETURN(1); /* A string with the formated time */
}

WSLUA_FUNCTION wslua_report_failure(lua_State* LS) { /* Reports a failure to the user. */
#define WSLUA_ARG_report_failure_TEXT 1 /* Message text to report. */
    const gchar* s = luaL_checkstring(LS,WSLUA_ARG_report_failure_TEXT);
    report_failure("%s",s);
    return 0;
}

/* The returned filename is g_malloc()'d so the caller must free it */
/* except when NULL is returned if file doesn't exist               */
char* wslua_get_actual_filename(const char* fname) {
    char fname_clean[256];
    char* f;
    char* filename;

    g_strlcpy(fname_clean,fname,255);
    fname_clean[255] = '\0';

    for(f = fname_clean; *f; f++) {
        switch(*f) {
            case '/': case '\\':
                *f = *(G_DIR_SEPARATOR_S);
                break;
            default:
                break;
        }
    }

    if ( file_exists(fname_clean) ) {
        return g_strdup(fname_clean);
    }

    filename = get_persconffile_path(fname_clean,FALSE);

    if ( file_exists(filename) ) {
        return filename;
    }
    g_free(filename);

    /*
     * Try to look in global data directory, nothing extraordinary for normal
     * installations. For executions from the build dir, it will look for files
     * copied to DATAFILE_DIR.
     */
    filename = get_datafile_path(fname_clean);
    if ( file_exists(filename) ) {
        return filename;
    }
    g_free(filename);

    return NULL;
}

WSLUA_FUNCTION wslua_loadfile(lua_State* L) {
    /*
    Loads a Lua file and compiles it into a Lua chunk, similar to the standard
    https://www.lua.org/manual/5.1/manual.html#pdf-loadfile[loadfile]
    but searches additional directories.
    The search order is the current directory, followed by the user's
    https://www.wireshark.org/docs/wsug_html_chunked/ChAppFilesConfigurationSection.html[personal configuration]
    directory, and finally the
    https://www.wireshark.org/docs/wsug_html_chunked/ChAppFilesConfigurationSection.html[global configuration]
    directory.

    ===== Example

    [source,lua]
    ----
    -- Assume foo.lua contains definition for foo(a,b). Load the chunk
    -- from the file and execute it to add foo(a,b) to the global table.
    -- These two lines are effectively the same as dofile('foo.lua').
    local loaded_chunk = assert(loadfile('foo.lua'))
    loaded_chunk()

    -- ok to call foo at this point
    foo(1,2)
    ----
    */
#define WSLUA_ARG_loadfile_FILENAME 1 /* Name of the file to be loaded. If the file does not exist in the current directory, the user and system directories are searched. */
    const char *given_fname = luaL_checkstring(L, WSLUA_ARG_loadfile_FILENAME);
    char* filename;

    filename = wslua_get_actual_filename(given_fname);

    if (!filename) {
        WSLUA_ARG_ERROR(loadfile,FILENAME,"file does not exist");
        return 0;
    }

    if (luaL_loadfile(L, filename) == 0) {
        g_free(filename);
        return 1;
    } else {
        g_free(filename);
        lua_pushnil(L);
        lua_insert(L, -2);
        return 2;
    }
}

WSLUA_FUNCTION wslua_dofile(lua_State* L) {
    /*
    Loads a Lua file and executes it as a Lua chunk, similar to the standard
    https://www.lua.org/manual/5.1/manual.html#pdf-dofile[dofile]
    but searches additional directories.
    The search order is the current directory, followed by the user's
    https://www.wireshark.org/docs/wsug_html_chunked/ChAppFilesConfigurationSection.html[personal configuration]
    directory, and finally the
    https://www.wireshark.org/docs/wsug_html_chunked/ChAppFilesConfigurationSection.html[global configuration]
    directory.
    */
#define WSLUA_ARG_dofile_FILENAME 1 /* Name of the file to be run. If the file does not exist in the current directory, the user and system directories are searched. */
    const char *given_fname = luaL_checkstring(L, WSLUA_ARG_dofile_FILENAME);
    char* filename = wslua_get_actual_filename(given_fname);
    int n;

    if (!filename) {
        WSLUA_ARG_ERROR(dofile,FILENAME,"file does not exist");
        return 0;
    }

    n = lua_gettop(L);
    if (luaL_loadfile(L, filename) != 0) lua_error(L);
    g_free(filename);
    lua_call(L, 0, LUA_MULTRET);
    return lua_gettop(L) - n;
}


typedef struct _statcmd_t {
    lua_State* L;
    int func_ref;
} statcmd_t;

static int statcmd_init_cb_error_handler(lua_State* L _U_) {
    return 0;
}

static void statcmd_init(const char *opt_arg, void* userdata) {
    statcmd_t* sc = (statcmd_t *)userdata;
    lua_State* L = sc->L;

    lua_settop(L,0);
    lua_pushcfunction(L,statcmd_init_cb_error_handler);
    lua_rawgeti(L, LUA_REGISTRYINDEX, sc->func_ref);

    lua_pushstring(L,opt_arg);

    switch ( lua_pcall(L,1,0,1) ) {
        case 0:
            break;
        case LUA_ERRRUN:
            g_warning("Runtime error while calling statcmd callback");
            break;
        case LUA_ERRMEM:
            g_warning("Memory alloc error while calling statcmd callback");
            break;
        default:
            g_assert_not_reached();
            break;
    }

}

WSLUA_FUNCTION wslua_register_stat_cmd_arg(lua_State* L) {
    /* Register a function to handle a `-z` option */
#define WSLUA_ARG_register_stat_cmd_arg_ARGUMENT 1 /* The name of the option argument. */
#define WSLUA_OPTARG_register_stat_cmd_arg_ACTION 2 /* The function to be called when the command is invoked. */
    const char* arg = luaL_checkstring(L,WSLUA_ARG_register_stat_cmd_arg_ARGUMENT);
    statcmd_t* sc = (statcmd_t *)g_malloc0(sizeof(statcmd_t)); /* XXX leaked */
    stat_tap_ui ui_info;

    sc->L = L;
    lua_pushvalue(L, WSLUA_OPTARG_register_stat_cmd_arg_ACTION);
    sc->func_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    lua_remove(L,1);

    ui_info.group = REGISTER_STAT_GROUP_UNSORTED;  /* XXX - need group for CLI-only? */
    ui_info.title = NULL;
    ui_info.cli_string = arg;
    ui_info.tap_init_cb = statcmd_init;
    ui_info.nparams = 0;
    ui_info.params = NULL;
    register_stat_tap_ui(&ui_info, sc);
    return 0;
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 8
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=8 expandtab:
 * :indentSize=4:tabSize=8:noTabs=true:
 */
